﻿using GalaSoft.MvvmLight;
using Daphne.Presenters;
using GameEngine;
using System.Reflection;
using GalaSoft.MvvmLight.Messaging;
using DaphneProfiBase;
using System.Windows;
using GalaSoft.MvvmLight.Command;
using System.Collections.Generic;
using Tomers.WPF.Localization;
using System.Linq;
using System.Linq.Expressions;

using EngineAdmin;
using System;
using System.Windows.Threading;


namespace DaphneControls.ViewModel
{
    /// <summary>
    /// View Model for Checkers Board
    /// </summary>
    /// 
    public class CheckersBoardVM : ViewModelBase
    {
        private Dispatcher _dispatcher;

        /// <summary>
        /// Checkers board settings
        /// </summary>
        private NewGameFormSettings _settings = null;
        private RunMatchSettings _matchSettings = null;
        private InfoEngineMatch _infoEngineMatch = null;

        private GameManager _gameManager;
        private ProfessionalGameBoardPresenter _presenter;

        // engine config
        private Engine _engine = null;
        private Engine _whiteEngine = null;
        private Engine _blackEngine = null;
        private IEngineProgress _engineProgress;
        private IEngineProgress _whiteProgress;
        private IEngineProgress _blackProgress;

        // undo/redo stacks
        private Stack<GameEngine.BitBoard> _positionsToUndo;
        private Stack<GameEngine.BitBoard> _positionsToRedo;

        private enumFigureColor _sideToMove;
        private GameEngine.BitBoard _bbSavedCheckersBoard;

        // random opening
        private int _openingMoveCount;
        private List<int> _openingIndexes;

        public CheckersBoardVM()
        {
            _dispatcher = Dispatcher.CurrentDispatcher;
            _positionsToUndo = new Stack<GameEngine.BitBoard>();
            _positionsToRedo = new Stack<GameEngine.BitBoard>();
            _openingIndexes = new List<int>();

            // init command
            IsActive = false;

            // register message for handling whether this board is active or not
            Messenger.Default.Register<NotificationMessage<CheckersBoardVM>>(this, (nm) =>
                {
                    if (nm.Notification == Notify.SetIsActiveBoard)
                    {
                        IsActive = nm.Content == this;
                        
                        // set the match progress settings
                        if(IsActive)
                            InformMatchSettings();
                        
                        // send editor state message
                        SendEditorStateMessage();
                    }
                });

            // register message for closing the checkers board and freeing the engine native code resources
            Messenger.Default.Register<NotificationMessage<object>>(this, (nm) =>
                {
                    if (nm.Notification == Notify.CloseCheckersBoard && nm.Content == this)
                    {
                        CloseEngines();
                    }
                });

            // registering message for receiving editor message
            Messenger.Default.Register<NotificationMessage<EditorCommand>>(this, (nm) =>
                {
                    if (IsActive && nm.Notification == Notify.EditorCommand)
                    {
                        HandleEditorMessage(nm.Content);
                    }
                });

            // register message for receiving global commands
            Messenger.Default.Register<NotificationMessage<GlobalCommandInfo>>(this, (nm) =>
                {
                    if (IsActive && nm.Notification == Notify.GlobalCommand)
                    {
                        HandleGlobalCommand(nm.Content.SubCommand);
                    }
                });
        }

        public bool IsInEditMode
        {
            get
            {
                return _presenter.IsInEditMode();
            }        
        }   
    
        public bool IsActive
        { get; set; }
            
        /// <summary>
        /// Initializaton, must be called after DataContext binding
        /// </summary>
        /// <param name="settings"></param>
        public void Initialize(NewGameFormSettings settings)
        {
            _settings = settings;

            // initialize starting position
            _gameManager = GameManagerFactory.CreateGameManager(_settings.CheckersType);
            _gameManager.NewGame();

            InitCommon();
        }

        /// <summary>
        /// Initialization for opening existing game
        /// </summary>
        /// <param name="gameManager"></param>
        public void Initialize(GameManager gameManager)
        {
            _gameManager = gameManager;
            InitCommon();
        }

        /// <summary>
        /// Initalization method for running an engine match
        /// </summary>
        /// <param name="settings"></param>
        public void Initialize(RunMatchSettings settings)
        {
            _matchSettings = settings;

            // initialize starting position
            _gameManager = GameManagerFactory.CreateGameManager(_matchSettings.CheckersType);
            _gameManager.NewGame();

            // init UI
            InitCommon(false);

            // match specific init
            _engine = null;
            _blackEngine = EngineAdministrator.Instance().CreateEngine(_matchSettings.BlackEngine);
            _whiteEngine = EngineAdministrator.Instance().CreateEngine(_matchSettings.WhiteEngine);
            _blackProgress = EngineAdministrator.Instance().LookUpProgress(_blackEngine);
            _whiteProgress = EngineAdministrator.Instance().LookUpProgress(_whiteEngine);
            _blackProgress.FinishedEvent += _engineProgress_FinishedEvent;
            _whiteProgress.FinishedEvent += _engineProgress_FinishedEvent;
                   
            _engine = _whiteEngine;

            // init info engine match class
            _infoEngineMatch = new InfoEngineMatch();
            _infoEngineMatch.CheckersType = settings.CheckersType;
            _infoEngineMatch.RoundsTotal = settings.Rounds;
            _infoEngineMatch.WhiteEngineConfig = settings.WhiteEngine;
            _infoEngineMatch.BlackEngineConfig = settings.BlackEngine;
            _infoEngineMatch.WhiteEngineOutput = new EngineMoveOutput();
            _infoEngineMatch.BlackEngineOutput = new EngineMoveOutput();
            _infoEngineMatch.RoundsPassed = 1;

            CreateRandomOpening(_infoEngineMatch.RoundsTotal);

            // start the match
            PlayRandomOpening(_infoEngineMatch.RoundsPassed - 1);
            DoComputerMove();
        }


        /// <summary>
        /// Common initialization
        /// </summary>
        private void InitCommon(bool initEngine = true)
        {
            // initialize engine
            if (initEngine)
            {
                InitEngine();
            }

            // init UI
            _presenter = new ProfessionalGameBoardPresenter();
            _presenter.GameManager = _gameManager;
            _presenter.MoveDoneEvent += new TMoveDoneDelegate(Presenter_MoveDoneEvent);
            _presenter.AnimationMoveFinishedEvent += _presenter_AnimationMoveFinishedEvent;
            _presenter.FigurePlacedEvent += _presenter_FigurePlacedEvent;

            // send message to set presenter to the board
            Messenger.Default.Send<NotificationMessage<ProfessionalGameBoardPresenter>>(
                    new NotificationMessage<ProfessionalGameBoardPresenter>(this, _presenter, Notify.SetPresenter));

            // broadcast setting the new game manager to all the application content
            Messenger.Default.Send<NotificationMessage<GameManager>>(
                new NotificationMessage<GameManager>(_gameManager, Notify.SetGameManager));

        }

        /// <summary>
        /// Closes the engines of either the engine match or a classic game
        /// </summary>
        private void CloseEngines()
        {
            if (_matchSettings == null)
            {
                EngineAdministrator.Instance().CloseEngine(_engine);
            }
            else
            {
                EngineAdministrator.Instance().CloseEngine(_whiteEngine);
                EngineAdministrator.Instance().CloseEngine(_blackEngine);
            }
        }

        private void InitEngine()
        {
            // DEBUG - for now switch off
            if (_settings == null)
                return;

            // for now no support for this engines
            if (_settings.CheckersType == enumGameType.Italian || _settings.CheckersType == enumGameType.Russian)
                return;

            // TO-DO Debug - engine settings
            EngineConfig config = new EngineConfig();
            config.EngineType = eEngineType.Internal;
            config.CheckersType = _settings.CheckersType.ToComputerType();
            config.InternalEngineSettings.MaxSeconds = 1;
            config.InternalEngineSettings.SearchType = ComputerEngine.eSearchType.AlphaBeta;
            config.InternalEngineSettings.UseCache = false;
            config.InternalEngineSettings.CacheSizeMB = 256;
            config.InternalEngineSettings.UseEndGameDB = false;
            config.InternalEngineSettings.UseKillerMoves = true;
            config.InternalEngineSettings.UseHistoryHeuristics = false;

            // call to create the engine
            _engine = EngineAdministrator.Instance().CreateEngine(config);

            // handle engine progress
            if (_engineProgress == null)
            {
                _engineProgress = EngineAdministrator.Instance().LookUpProgress(_engine);
                _engineProgress.FinishedEvent += _engineProgress_FinishedEvent;
            }
        }



        /// <summary>
        /// Move event handler
        /// </summary>
        private void Presenter_MoveDoneEvent(Move move)
        {
            DoMove(move);
            //uxBoard2D.InvalidateVisual();

            if (_settings.OnePlayerGame)
            {
                DoComputerMove();
            }
        }

        /// <summary>
        /// Does the move
        /// </summary>
        private void DoMove(Move move)
        {
            _presenter.GameManager.FinalizeMove(move);
            //uxBoard2D.InvalidateVisual();
        }

        /// <summary>
        /// Start the computer thinking
        /// </summary>
        private void DoComputerMove()
        {
            // create Game State Object for Engine
            GameStateConfig config = new GameStateConfig();
            config.CheckersType = _settings != null ? _settings.CheckersType.ToComputerType()
                : _matchSettings.CheckersType.ToComputerType();
            config.WhiteToMove = _gameManager.GameBoard.OnMove == enumFigureColor.White;
            config.CheckersBoard = MoveHelper.ConvertCheckersBoard(_gameManager.GameBoard);
            config.GameHistory = MoveHelper.ConvertMoveHistory(_gameManager);

            // compute the move
            IEngineProgress progress = EngineAdministrator.Instance().ComputeMove(_engine, config);
        }

        private void ComputerMoveDone(EngineAdmin.BoardPos[] compMove, EngineMoveOutput moveOutput)
        {
            // complete the move
            /////////////////////////////////////////////////////////////////////////////////
            var move = compMove.ToList();
            if (move.Count < 2)
                throw new ApplicationException("ComputerMove: Position.Count < 2");

            BoardPos startPos = move[0];
            BoardPos lastPos = move[1];
            move.Remove(startPos);
            move.Remove(lastPos);

            Move finalMove = _gameManager.CompleteMoveStep(new Position(startPos.Row, startPos.Col),
                                new Position(lastPos.Row, lastPos.Col));
            _gameManager.DoMoveStep(finalMove);
            while (move.Count > 0)
            {
                BoardPos finPos = move[0];
                Move curMove = _gameManager.CompleteMoveStep(new Position(lastPos.Row, lastPos.Col),
                    new Position(finPos.Row, finPos.Col));
                _gameManager.DoMoveStep(curMove);

                Move lastMove = finalMove;
                while (lastMove.nextMove != null)
                    lastMove = lastMove.nextMove;
                lastMove.nextMove = curMove;

                lastPos = finPos;
                move.Remove(finPos);
            }

            _gameManager.FinalizeMove(finalMove);
            _gameManager.MoveHistory.MoveBack();

            // copy the engine move output
            if (_matchSettings != null)
            {               
                if (_gameManager.GetOnMove() == enumFigureColor.White)
                {
                    _infoEngineMatch.WhiteEngineOutput = moveOutput;
                }
                else
                {
                    _infoEngineMatch.BlackEngineOutput = moveOutput;
                }

                _gameManager.MoveHistory.MoveForward();
                RedrawBoard();

                // show the engine match progress information
                if (IsActive)
                    InformMatchSettings();

                EngineMatchFinalMoveEval();

                return;
            }

            // animate the move
            _presenter.AnimateMove(finalMove);
        }

        /// <summary>
        /// Engine finished computation handler
        /// </summary>
        void _engineProgress_FinishedEvent(object sender, EngineFinishedEventArgs e)        
        {            
            _dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
                {
                    var move  = e.Move.ToArray();
                    ComputerMoveDone(move, e.Output);
                }));
        }

        private void EngineMatchFinalMoveEval()
        {
            enumGameResult gameResult = _gameManager.EvaluateGameResult();

            if (gameResult == enumGameResult.Unknown)
            {
                SwapEngines();
                DoComputerMove();
            }
            else
            {
                // update game engine
                switch (gameResult)
                {
                    case enumGameResult.WhiteWin:
                        _infoEngineMatch.WhiteWins++;
                        break;
                    case enumGameResult.BlackWin:
                        _infoEngineMatch.BlackWins++;
                        break;
                    case enumGameResult.Draw:
                        _infoEngineMatch.Draws++;
                        break;
                }

                // show the engine match progress information
                if (IsActive)
                    InformMatchSettings();


                // check the end of match
                if (_infoEngineMatch.RoundsPassed >= _infoEngineMatch.RoundsTotal)
                    return;

                // start the new match
                _infoEngineMatch.RoundsPassed++;
                _engine = _whiteEngine;
                _gameManager.NewGame();
                PlayRandomOpening(_infoEngineMatch.RoundsPassed - 1);
                DoComputerMove();
            }
        }




        /// <summary>
        /// Move animation finished handler
        /// </summary>
        void _presenter_AnimationMoveFinishedEvent(object sender, EventArgs e)
        {
            _gameManager.MoveHistory.MoveForward();
        }

        private void InformMatchSettings()
        {
            if (_matchSettings == null)
            {
                // no match running in the current view
                Messenger.Default.Send<NotificationMessage>(new NotificationMessage(Notify.NoEngineMatchInfo));
            }
            else
            {
                // broadcast the match progress information message
                Messenger.Default.Send<NotificationMessage<InfoEngineMatch>>(
                    new NotificationMessage<InfoEngineMatch>(_infoEngineMatch, Notify.EngineMatchInfo));
            }
        }

        #region Position Editing Methods

        /// <summary>
        /// Sends the state message
        /// </summary>
        private void SendEditorStateMessage()
        {
            if (IsActive)
            {
                // create the state container class
                EditorStateCommand cmd = new EditorStateCommand();
                cmd.IsInEditMode = _presenter.IsInEditMode();
                if (cmd.IsInEditMode)
                {
                    cmd.UndoEnabled = _positionsToUndo.Count > 0;
                    cmd.RedoEnabled = _positionsToRedo.Count > 0;
                    cmd.SideToMove = _sideToMove;
                }

                // send the message
                Messenger.Default.Send<NotificationMessage<EditorStateCommand>>(
                    new NotificationMessage<EditorStateCommand>(cmd, Notify.SetEditorState));
            }
        }

        /// <summary>
        /// Handles the editor message
        /// </summary>
        private void HandleEditorMessage(EditorCommand cmd)
        {
            // choose among sub-commands
            switch (cmd.SubCommand)
            {
                case Notify.StartEditing:
                    _positionsToRedo.Clear();
                    _positionsToUndo.Clear();
                    _bbSavedCheckersBoard = _gameManager.GameBoard.PackGameBoard();
                    _presenter.ToggleEditMode();
                    break;
                case Notify.SetStartPositon:
                    _positionsToRedo.Clear();
                    _positionsToUndo.Push(_gameManager.GameBoard.PackGameBoard());
                    _gameManager.GameBoard.SetupStartPosition(_gameManager.PieceRows);
                    break;
                case Notify.ClearCheckersBoard:
                    _positionsToRedo.Clear();
                    _positionsToUndo.Push(_gameManager.GameBoard.PackGameBoard());
                    _gameManager.GameBoard.CleanBoard();
                    break;
                case Notify.PositionUndo:
                    if(_positionsToUndo.Count > 0) {

                        GameEngine.BitBoard bb = _positionsToUndo.Pop();
                        _positionsToRedo.Push(_gameManager.GameBoard.PackGameBoard());
                        _gameManager.GameBoard.UnpackGameBoard(bb);
                    }
                    break;
                case Notify.PositionRedo:
                    if (_positionsToRedo.Count > 0)
                    {
                        GameEngine.BitBoard bb = _positionsToRedo.Pop();
                        _positionsToUndo.Push(_gameManager.GameBoard.PackGameBoard());
                        _gameManager.GameBoard.UnpackGameBoard(bb);
                    }
                    break;
                case Notify.ParseFEN:
                    MessageBox.Show("Not supported yet.");
                    break;
                case Notify.SetSideToMove:
                    _sideToMove = cmd.SideToMove;
                    break;
                case Notify.ApprovePosition:
                    _positionsToRedo.Clear();
                    _positionsToUndo.Clear();
                    _gameManager.GameBoard.SetOnMove(_sideToMove);
                    _presenter.ToggleEditMode();
                    break;
                case Notify.CancelEditing:
                    _positionsToRedo.Clear();
                    _positionsToUndo.Clear();
                    _gameManager.GameBoard.UnpackGameBoard(_bbSavedCheckersBoard);
                    _presenter.ToggleEditMode();
                    break;
            }

            RedrawBoard();
            SendEditorStateMessage();
        }

        /// <summary>
        /// Method for handling global commands
        /// </summary>
        private void HandleGlobalCommand(string subCommand)
        {
            // switch among global commands
            switch (subCommand)
            {
                case Notify.MoveNow:    // force engine to move
                    DoComputerMove();
                    break;
            }
        }

        /// <summary>
        /// Figure placed event handler in the setup-mode
        /// </summary>
        void _presenter_FigurePlacedEvent(object sender, FigurePlacedArgs e)
        {
            if (IsInEditMode && IsActive)
            {
                _positionsToRedo.Clear();
                _positionsToUndo.Push(e.SavedPosition);
                RedrawBoard();
                SendEditorStateMessage();
            }
        }


        #endregion

        /// <summary>
        /// Redraws the board
        /// </summary>
        private void RedrawBoard()
        {        
            // send message to the view
            Messenger.Default.Send<NotificationMessage>(new NotificationMessage(this, Notify.RedrawCheckersBoard));
        }

        /// <summary>
        ///  checks, whether game has finished
        /// </summary>
        private bool HasGameFinished()
        {
            // TO-DO: complete displaying messages
            // not pop up window, but window above the checkers board disappearing by click into it
            if (_gameManager.EvaluateGameResult() != enumGameResult.Unknown)
                return true;                  

            return false;
        }

        /// <summary>
        /// Swaps the engines
        /// </summary>
        private void SwapEngines()
        {
            _engine = _engine == _whiteEngine ? _blackEngine : _whiteEngine;
        }

        /// <summary>
        /// Creates the random list of openings for engine match
        /// </summary>
        private void CreateRandomOpening(int maxCount)
        {
            _openingMoveCount = EngineAdministrator.Instance().GetOpeningMoveCount(_engine);

            Random rnd = new Random();
            _openingIndexes.Clear();
            for (int i = 0; i < maxCount; i++)
            {
                int index = -1;

                do
                {
                    index = rnd.Next() % _openingMoveCount;
                } while (_openingIndexes.Contains(index) == true);
                
                _openingIndexes.Add(index);
            }
        }

        /// <summary>
        /// Plays random opening
        /// </summary>
        private void PlayRandomOpening(int index)
        {
            // create Game State Object for Engine
            GameStateConfig config = new GameStateConfig();
            config.CheckersType = _settings != null ? _settings.CheckersType.ToComputerType()
                : _matchSettings.CheckersType.ToComputerType();
            config.WhiteToMove = _gameManager.GameBoard.OnMove == enumFigureColor.White;
            config.CheckersBoard = MoveHelper.ConvertCheckersBoard(_gameManager.GameBoard);
            config.GameHistory = MoveHelper.ConvertMoveHistory(_gameManager);

            var list = EngineAdministrator.Instance().GetOpeningByIndex(_engine, _openingIndexes[index], config);
            foreach (var moveItem in list)
            {
                // complete the move
                /////////////////////////////////////////////////////////////////////////////////
                var move = moveItem.ToList();
                if (move.Count < 2)
                    throw new ApplicationException("ComputerMove: Position.Count < 2");

                BoardPos startPos = move[0];
                BoardPos lastPos = move[1];
                move.Remove(startPos);
                move.Remove(lastPos);

                Move finalMove = _gameManager.CompleteMoveStep(new Position(startPos.Row, startPos.Col),
                                    new Position(lastPos.Row, lastPos.Col));
                _gameManager.DoMoveStep(finalMove);
                while (move.Count > 0)
                {
                    BoardPos finPos = move[0];
                    Move curMove = _gameManager.CompleteMoveStep(new Position(lastPos.Row, lastPos.Col),
                        new Position(finPos.Row, finPos.Col));
                    _gameManager.DoMoveStep(curMove);

                    Move lastMove = finalMove;
                    while (lastMove.nextMove != null)
                        lastMove = lastMove.nextMove;
                    lastMove.nextMove = curMove;

                    lastPos = finPos;
                    move.Remove(finPos);
                }

                _gameManager.FinalizeMove(finalMove);

            }
        }
        
    }
}